/*
 * parsingUtils.c
 *
 *  Created on: Oct 25, 2011
 *      Author: shaohong
 */

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>


#include "parsingUtils.h"
#include "logger.h"
#include "fileLockHelper.h"


static const char * MODULE_NAME = "parsingUtils";

#define LINE_MAX 2048

const static char * CPP_COMMENT_TOKEN = "//";
const static char * C_COMMENT_START_TOKEN = "/*";
const static char * C_COMMENT_END_TOKEN = "*/";

/**
 * The variable to cache the parsing result.
 */
static LineParsingContext resultParsingContext;

/* A buffer that holds the result of line parsing. Assume one line is shorter than 1024 */
static char resultLineBuffer[LINE_MAX];

static volatile sig_atomic_t cancleCleaningFlag = 0;

/**
 * cancel the code cleaning job
 */
void cancelCleaning(){
	cancleCleaningFlag = 1;
}

/*
 * check if the cleaning job canceled.
 */
bool isJobCancled()
{
	return (1==cancleCleaningFlag);
}
/**
 * Check if the given line is empty
 */
bool isEmptyLine(const char * line) {
	if (NULL == line)
		return true;

	/**
	 * loop through the string and see if they're all "space"
	 */
	char * charPtr = (char *) line;
	while ('\0' != (*charPtr)) {
		if (!isspace(*charPtr)) {
			return false;
		}

		charPtr++;
	}

	return true;
}

/**
 * parsing one line with the given context.
 *
 * return the parsingContext after processing this line.
 * returns NULL if we failed to parse this line!
 */

LineParsingContext * getCodePartFromLine(LineParsingContext * initialContext) {

	assert(NULL != initialContext);

	if (isEmptyLine(initialContext->codeLine)) {
		/* if the given line is empty, the state doesn't change and there is no valuable code */
		resultParsingContext.parserState = initialContext->parserState;
		resultParsingContext.codeLine = NULL;

		return &resultParsingContext;
	}

	memset(resultLineBuffer, 0, LINE_MAX);

	if (initialContext->parserState == NORMAL_LINE) {

		// check if this line contains "//"
		char * cppCommentStartP = strstr(initialContext->codeLine,
				CPP_COMMENT_TOKEN);

		if (NULL != cppCommentStartP) {
			// the line contains the cpp comment token "//"

			// check if this line ends with "\n";
			bool endsWithNewLine = false;
			if ('\n'
					== *(initialContext->codeLine
							+ strlen(initialContext->codeLine) - 1)) {
				endsWithNewLine = true;
			}
			// we throw away anything after "//" and save the rest into a new string
			char * newStr = strndup(initialContext->codeLine,
					cppCommentStartP - initialContext->codeLine);

			initialContext->codeLine = newStr;
			LineParsingContext * newResultParsingContext = getCodePartFromLine(
					initialContext);

			if ((NULL != newResultParsingContext) && (NULL != newResultParsingContext->codeLine)) {
					strcpy(resultLineBuffer, newResultParsingContext->codeLine);

					//special handling if "\n" once exist at the end of the line
					if (endsWithNewLine){
						int l = strlen(resultLineBuffer);
						if ('\n' != resultLineBuffer[l-1]) {
							 resultLineBuffer[l] = '\n';
							 resultLineBuffer[l+1] = '\0';
						}
					}
					newResultParsingContext->codeLine = resultLineBuffer;
			}

			free(newStr);

			return newResultParsingContext;
		}

		// check if this line contains "/*"
		char * cCommentStartP = strstr(initialContext->codeLine,
				C_COMMENT_START_TOKEN);

		if (NULL != cCommentStartP) {
			// the line contains the c comment token "/*"
			// anything before "/*" are potential valuable code.
			strncpy(resultLineBuffer, initialContext->codeLine,
					(cCommentStartP - initialContext->codeLine));

			// now move the cursor to the start of "/*"
			char * cCommentEndP = strstr(cCommentStartP, C_COMMENT_END_TOKEN);

			if (NULL == cCommentEndP) {
				// this line contains "/*" but doesn't contain "*/"
				// this is a multiple line comments scenario
				resultParsingContext.parserState = IN_BLOCK_COMMENTING;
			} else {
				resultParsingContext.parserState = NORMAL_LINE;

				//anything after "*/" are valuable code.
				strncpy(resultLineBuffer + strlen(resultLineBuffer),
						cCommentEndP + 2, strlen(cCommentEndP) - 2);
			}

			// make sure we don't return an empty line!
			if (!isEmptyLine(resultLineBuffer)) {
				resultParsingContext.codeLine = resultLineBuffer;
			} else {
				resultParsingContext.codeLine = NULL;
			}
			return &resultParsingContext;
		}

		// there is no "//" or "/*" in the line, so it's a pure code line
		// and we know it's not empty (see the beginning part of this function)
		resultParsingContext.parserState = NORMAL_LINE;
		resultParsingContext.codeLine = initialContext->codeLine;
		return &resultParsingContext;

	}

	if (initialContext->parserState == IN_BLOCK_COMMENTING) {

		// check if this line contains "//"
		char * cppCommentStartP = strstr(initialContext->codeLine,
				CPP_COMMENT_TOKEN);

		if (NULL != cppCommentStartP) {
			// we throw away anything after "//" and save the rest into a new string
			// and parse that string
			char * newStr = strndup(initialContext->codeLine,
					cppCommentStartP - initialContext->codeLine);

			initialContext->codeLine = newStr;

			LineParsingContext * newResultParsingContext = getCodePartFromLine(
					initialContext);

			if (NULL != newResultParsingContext) {
				if (NULL != newResultParsingContext->codeLine) {
					strcpy(resultLineBuffer, newResultParsingContext->codeLine);
					newResultParsingContext->codeLine = resultLineBuffer;
				}
			}

			free(newStr);

			return newResultParsingContext;
		}

		// if we're in the middle of BLCOK comment, let's see if there is an end of comment token
		char * cCommentEndP = strstr(initialContext->codeLine,
				C_COMMENT_END_TOKEN);

		if (NULL == cCommentEndP) {
			//we're still in block comments
			resultParsingContext.parserState = IN_BLOCK_COMMENTING;
		} else {
			//block comment ended
			resultParsingContext.parserState = NORMAL_LINE;

			//anything after "*/" are valuable code.
			strncpy(resultLineBuffer + strlen(resultLineBuffer),
					cCommentEndP + 2, strlen(cCommentEndP) - 2);
		}

		// make sure we don't return an empty line!
		if (!isEmptyLine(resultLineBuffer)) {
			resultParsingContext.codeLine = resultLineBuffer;
		} else {
			resultParsingContext.codeLine = NULL;
		}
		return &resultParsingContext;
	}

	return NULL;
}

/* clean the source code for the given file */
int cleanSourceCode(const char * sourceFileName) {
	char msgBuf[LINE_MAX];

	logDebugVarg(MODULE_NAME, "cleaning file: \"%s\"", sourceFileName);
	char tmpOputputFileName[200], finalOutputFileName[200];

	//generate the final and temp file name
	sprintf(finalOutputFileName, "%s.clean", sourceFileName);
	sprintf(tmpOputputFileName, "%s.tmp", finalOutputFileName);

	FILE * inputStream = fopen(sourceFileName, "r");
	if (NULL == inputStream) {
		logDebugVarg(MODULE_NAME, "Failed to open file %s", sourceFileName);
		sprintf(msgBuf, "Failed to open file %s\n", sourceFileName);
		perror(msgBuf);
		return EXIT_FAILURE;
	}

	// make a temp file
	FILE * outputStream = fopen(tmpOputputFileName, "w");
	if (NULL == outputStream) {
		logDebugVarg(MODULE_NAME, "Failed to create file %s",
				tmpOputputFileName);
		sprintf(msgBuf, "Failed to create file %s\n", tmpOputputFileName);
		perror(msgBuf);
		exit(EXIT_FAILURE);
	}


	// Put a read lock on the input stream
	if (EXIT_FAILURE == setFileLock(fileno(inputStream), F_RDLCK)) {
		fprintf(stderr, "PID: %ld failed to get file read lock \n",
				(long) getpid());
		logDebugVarg(MODULE_NAME, "failed to get file read lock");
		exit(EXIT_FAILURE);
	}
	logDebugVarg(MODULE_NAME, "file read lock for \"%s\" acquired!", sourceFileName);

	// read line by line from the inputfile, parse it and save the parse result to output file
	LineParsingContext initialContext;
	initialContext.parserState = NORMAL_LINE;

	char *line = NULL;
	size_t len = 0;
	ssize_t read;

	int lineNo = 0;
	while (!isJobCancled() && ((read = getline(&line, &len, inputStream)) != -1)) {
		lineNo++;
		initialContext.codeLine = line;

		LineParsingContext * resultContext = getCodePartFromLine(
				&initialContext);
		if (NULL == resultContext) {
			logDebugVarg(MODULE_NAME, "failed to parse line:\n %s", line);
			sprintf(msgBuf, "failed to parse line:\n %s", line);
			exit(EXIT_FAILURE);
		}

		if ((resultContext->codeLine != NULL)
				&& (strlen(resultContext->codeLine) > 0)) {
			//write the parse result to outputfile
			fprintf(outputStream, "%s", resultContext->codeLine);
		}

		initialContext.parserState = resultContext->parserState;

		/*Here we introduce some artificial sleep to slow things down*/
		if (0 == (lineNo % 100)) {
			logDebugVarg(MODULE_NAME,
					"processed %d lines of source code so far.", lineNo);

#ifdef SNAIL_VERSION

			sleep(1);
#endif
		}

	}

	logDebugVarg(MODULE_NAME, "Processed totally %d lines of source code.", lineNo);

	logDebugVarg(MODULE_NAME, "releasing file read lock for \"%s\"", sourceFileName);
	setFileLock(fileno(inputStream), F_UNLCK);

	fclose(inputStream);
	fclose(outputStream);

	if (isJobCancled()) {
		/*
		 * if we're interrupted, then the intermediate file is meaningless, just remove it
		 */
		logDebugVarg(MODULE_NAME, "code cleaning job is canceled by user!!!");
		logDebugVarg(MODULE_NAME, "removing temp file: %s", tmpOputputFileName);
		unlink(tmpOputputFileName);
		exit(EXIT_FAILURE);

	} else {

		//rename the tempfile to final output fileName
		logDebugVarg(MODULE_NAME, "renaming file \"%s\" to \"%s\"",
				tmpOputputFileName, finalOutputFileName);
		if (-1 == rename(tmpOputputFileName, finalOutputFileName)) {
			fprintf(stderr, "failed to rename file from %s to %s\n",
					tmpOputputFileName, finalOutputFileName);
			return EXIT_FAILURE;
		}

		logDebugVarg(MODULE_NAME,
				"done cleaning file: \"%s\". clean code is saved as \"%s\"",
				sourceFileName, finalOutputFileName);

		return 0;
	}

}

